﻿using System;
using Bettzueche.RLGlue.RLComponent;
using Bettzueche.RLGlue.RLTypes;

namespace Bettzueche.RLGlue.Codec.Network {

    /// <summary>
    /// Middleware between Environment Impl and RLGlue.
    /// </summary>
    /// <remarks>
    /// This Code is based on the Java RL-Glue <a href="http://rl-glue-ext.googlecode.com/">rl-glue-ext.googlecode.com/</a>,
    /// Copyright (C) 2007, Brian Tanner.
    /// <para>
    /// The ClientEnvironment is used by the EnvironmentLoader, for example, and encapsulates the network communication.
    /// For running an environment implementation in your experiment, you can use the <see cref="Bettzueche.RLGlue.Codec.Util.EnvironmentLoader"/> or
    /// this ClientEnvironment like folling example:
    /// </para>
    /// </remarks>
    /// <example>
    /// <code>
    /// IClientEnvironment theClient = new ClientEnvironment(myEnvironmentImplInstance);
    /// int retryTimeout = 2;
    /// theClient.Connect(host, port, retryTimeout);
    /// theClient.RunEnvironmentEventLoop();
    /// theClient.Close();     
    /// </code>
    /// </example>
    public class ClientEnvironment : AbstractClientEnvironment {


        /// <summary>Referenz auf die Environment Implementierung.</summary>
        private IEnvironment env;

        /// <summary>
        /// New Middleware connection to RLGlue.
        /// </summary>
        /// <param name="envmnt">Your Environment.</param>
        /// <exception cref="ArgumentNullException"></exception>
        public ClientEnvironment(IEnvironment envmnt) {
            if (envmnt == null)
                throw new ArgumentNullException();
            this.env = envmnt;
            this.network = new Network();
        }


        /// <summary>
        /// Handles a EnvMessage RMI.
        /// </summary>
        protected override void onEnvMessage() {
            String message = network.GetString();
            String reply = env.EnvMessage(message);

            network.ClearSendBuffer();
            network.PutInt(Protocol.kEnvMessage);
            network.PutInt(Network.SizeOf(reply));
            network.PutString(reply);
        }

        /// <summary>
        /// Handles a EnvCleanup RMI.
        /// </summary>
        protected override void onEnvCleanup() {
            env.CleanupEnvironment();
            network.ClearSendBuffer();
            network.PutInt(Protocol.kEnvCleanup);
            network.PutInt(0);
        }

        /// <summary>
        /// Handles a EnvStep RMI.
        /// </summary>
        protected override void onEnvStep() {
            GeneralAction action = network.GetAction();
            double reward;
            GeneralState observation;
            bool terminal = env.StepEnvironment(action, out reward, out observation);

            network.ClearSendBuffer();
            network.PutInt(Protocol.kEnvStep);
            //sizeof reward + observation + terminal:
            network.PutInt(Protocol.kDoubleSize + Network.SizeOf(observation) + Protocol.kIntSize);

            network.PutRewardObservation(reward, observation, terminal);
        }

        /// <summary>
        /// Handles a EnvStart RMI.
        /// </summary>
        protected override void onEnvStart() {
            GeneralState obs = env.StartEnvironment();

            network.ClearSendBuffer();
            network.PutInt(Protocol.kEnvStart);
            network.PutInt(Network.SizeOf(obs));
            network.PutObservation(obs);
        }

        /// <summary>
        /// Handles a EnvInit RMI.
        /// </summary>
        protected override void onEnvInit() {
            String taskSpec = env.InitEnvironment().ToTaskSpecString();

            network.ClearSendBuffer();
            network.PutInt(Protocol.kEnvInit);
            network.PutInt(Network.SizeOf(taskSpec)); // This is different than taskSpec.length(). It also includes
            network.PutString(taskSpec);
        }
    }
}
